#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

using namespace std;

namespace Server
{
    static const string defaultIp = "0.0.0.0"; // 字符串式的点分十进制表示
    static const int gnum = 1024;

    enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR, OPEN_ERR};
    
    typedef function<void(int, string, uint16_t, string)> func_t;

    class udpServer
    {
    public:
        udpServer(const func_t &cb, const uint16_t &port, const string &ip = defaultIp)
            :_callback(cb), _port(port), _ip(ip), _sockfd(-1)
        {
        }
        void initServer()
        {
            // 1.创建套接字
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if(_sockfd == -1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(SOCKET_ERR);
            }
            cout << "server socket success : " << _sockfd << endl;
            // 2.绑定port[服务器要明确的端口号,不能随意改变], ip(告诉操作系统_sockfd套接字的port和ip是多少)
            struct sockaddr_in local;// 在用户栈上定义了个变量
            bzero(&local, sizeof(local)); // 使用memset也可以, 我们见见bzero这种写法
            local.sin_family = AF_INET;
            local.sin_port = htons(_port); // 你要给对方发消息, 你的port和ip都要发给对方
            local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1.string->uint32_t  2.htonl(uint32_t); 3.C语言的结构体不能被整体赋值, 只能是字段赋值
            // local.sin_addr.s_addr = INADDR_ANY; // 任意地址bind, 服务器的真实写法, INADDR_ANY已经是一个网络字节序的值, 不需要再进行转换

            int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); // sizeof运算符返回size_t类型
            if(n == -1) 
            {
                cerr << "bind error: " << errno << " : " << strerror(errno) << endl;
                exit(BIND_ERR);
            }
            // UDP服务器预备工作完成
        }
        void start()
        {
            // ---服务器读取数据
            // 服务器的本质就是一个死循环, 常驻内存的进程最害怕内存泄露, 例如操作系统软件就是死循环进程, 只有人为断电才能结束OS软件进程
            char buffer[gnum];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);  
            for(;;)
            {
                ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
                // 把对方发过来的数据当作字符串处理, 给buffer留一个\0位置
                // 1.数据是什么? 2.谁发的
                if(s > 0)
                {
                    buffer[s] = 0;//当作字符串处理
                    string clientIp = inet_ntoa(peer.sin_addr); // 1.网络序列  2.uint32_t->string点分十进制ip
                    uint16_t clientPort = ntohs(peer.sin_port); // 网络序列转主机序列
                    string message = buffer;

                    cout << "[" << clientIp << "]" << " [" << clientPort << "]# " << message << endl;
                    //我们只把数据读上来就完了吗？对数据作处理
                    // _callback(_sockfd, clientIp, clientPort, message);
                }
            }
        }
        ~udpServer()
        {
        }

    private:
        uint16_t _port; // 端口号是2字节16位的整数
        string _ip;     // TODO 实际上, 一款网络服务器, 不建议指明一个ip
        int _sockfd;
        func_t _callback;// 回调

        // static const int num = 10;//C++给整数开了后门，允许在类内定义static变量，这样在定义数组时会更方便一些。
        // int _arr[num];
    };

}
